
package cz.czu.pef.DataModelling.utils;

import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManagerFactory;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.collection.AbstractPersistentCollection;
import org.hibernate.impl.SessionImpl;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;


// Inspired by: http://9mmedia.com/blog/?p=272
@SuppressWarnings("unused")
/* Allows aspect to be configurable (@Autowired works) - needs this in Spring config:
	<context:spring-configured/>
	<aop:aspectj-autoproxy proxy-target-class="true"/>
*/ 
@Component
@Configurable
@Aspect
public class HibernateReattachSessionAspect
{

	private static final Logger logger = Logger.getLogger(HibernateReattachSessionAspect.class.getName());

	private SessionFactory sessionFactory;

  public void setSessionFactory(SessionFactory sessionFactory)
  {
      this.sessionFactory = sessionFactory;
  }

	private class ThreadLocaleInit<T> extends ThreadLocal<T>
	{

		protected T initVal;

		public ThreadLocaleInit(T initvalue)
		{
			initVal = initvalue;
		}

		@Override
		protected T initialValue()
		{
			return initVal;
		}
	}

	protected ThreadLocal<Boolean> locked = new ThreadLocaleInit<Boolean>(false);

	private Session getHibernateSession()
	{
		Session session;

		// 1st approach (needs bean definition in Spring config)
		// http://stackoverflow.com/questions/4148231/how-can-i-get-the-session-object-if-i-have-the-entitymanager
		//org.hibernate.Session session = (Session) entityManager.getDelegate(); // JPA 1.0 spec
		//org.hibernate.Session session = (Session) entityManager.unwrap(Session.class); // JPA 2.0 spec
		//session = entityManager.unwrap(Session.class); // JPA 2.0 spec style

		// 2nd approach
		//session = SessionFactoryUtils.getSession((SessionFactory)applicationContext.getBean("sessionFactory"), false /*allowCreate*/);

		// 3rd approach, needs bean definition in Spring config
		session = SessionFactoryUtils.getSession(sessionFactory, false /*allowCreate*/);
		return session;
	}

	private Object reattachSession(ProceedingJoinPoint pjp) throws Throwable
	{
		try
		{
			logger.finest("reattachSession():: Entry");
			Object obj = null;
			obj = pjp.proceed();
			if (!locked.get())
			{
				boolean reAttachNeeded = false;
				if (obj instanceof AbstractPersistentCollection)
				{
					AbstractPersistentCollection ps = (AbstractPersistentCollection) obj;
					reAttachNeeded = !ps.wasInitialized() && ps.getSession() == null;
				}

				if (obj instanceof HibernateProxy)
				{
					LazyInitializer li = ((HibernateProxy) obj).getHibernateLazyInitializer();
					reAttachNeeded = li.isUninitialized() && li.getSession() == null;
				}

				if (reAttachNeeded)
				{
					if (logger.isLoggable(Level.FINEST))
					{
						logger.finest("reattachSession():: Re-attaching Hibernate session to " + pjp.toString());
					}

					SessionImpl source = (SessionImpl) getHibernateSession();

					locked.set(Boolean.TRUE);
					try
					{
						// This is the essential part of the code that re-attaches
						// the domain entity to the database session.
						source.buildLockRequest(LockOptions.NONE).lock(pjp.getTarget());
					}
					catch (Exception ex)
					{
						logger.log(Level.SEVERE, "reattachSession():: Error in HibernateReattachSessionAspect", ex);
						throw ex;
					}
					finally
					{
						locked.set(false);
					}
				}
			}

			return obj;
		}
		finally
		{
			logger.finest("reattachSession():: Exit");
		}
	}


	// the execution of any method defined in the domain objects package:
	@Around("methodsToBeReattachedForJpaDomainObjects()")
	public Object reattachSessionForJpaDomainObjects(ProceedingJoinPoint pjp) throws Throwable
	{
		try
		{
			logger.finer("reattachSessionForJpaDomainObjects():: Entry");
			return reattachSession(pjp);
		}
		finally
		{
			logger.finer("reattachSessionForJpaDomainObjects():: Exit");
		}
	}
	@Pointcut("execution(* cz.czu.pef.DataModelling.*.*(..))")
	public void methodsToBeReattachedForJpaDomainObjects()
	{
	}


/*
	// the execution of any method with mentioned JPA annotations under any "cz.*" package
	@Around("methodsToBeReattachedForJpaAnnotations()")
	public Object reattachSessionForJpaAnnotations(ProceedingJoinPoint pjp) throws Throwable
	{
		try
		{
			logger.finer("reattachSessionForJpaAnnotations():: Entry");
			return reattachSession(pjp);
		}
		finally
		{
			logger.finer("reattachSessionForJpaAnnotations():: Exit");
		}
	}
	@Pointcut("execution( @(javax.persistence.OneToMany || javax.persistence.ManyToMany || javax.persistence.ManyToOne || javax.persistence.OneToOne) * cz..*.*(..))")
	public void methodsToBeReattachedForJpaAnnotations()
	{
	}
*/

	/*
	// the execution of any method having @Lazy annotation
	@Around("methodsToBeReattachedForLazyAnnotation()")
	public Object reattachSessionForLazyAnnotation(ProceedingJoinPoint pjp) throws Throwable
	{
		try
		{
			logger.finer("reattachSessionForLazyAnnotation():: Entry");
			return reattachSession(pjp);
		}
		finally
		{
			logger.finer("reattachSessionForLazyAnnotation():: Exit");
		}
	}
	@Pointcut("execution(@cz.czu.pef.DataModelling.dao.Lazy * *(..))")
	public void methodsToBeReattachedForLazyAnnotation()
	{
	}
	*/
}